﻿/*
 * Copyright 2007-2008  Lukasz Laszko lukaszlaszko@gmail.com
 * 
 * Permission to use, copy, modify, and distribute this software
 * and its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice appear in all
 * copies and that both that the copyright notice and this
 * permission notice and warranty disclaimer appear in supporting
 * documentation, and that the name of the author not be used in
 * advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.
 * 
 * The author disclaim all warranties with regard to this
 * software, including all implied warranties of merchantability
 * and fitness.  In no event shall the author be liable for any
 * special, indirect or consequential damages or any damages
 * whatsoever resulting from loss of use, data or profits, whether
 * in an action of contract, negligence or other tortious action,
 * arising out of or in connection with the use or performance of
 * this software. 
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Pg.BioMedics.SDR.Entities;

namespace Pg.BioMedics.SDR.Services.Algorithms
{
    /// <summary>
    /// Implementation of Okapi formula for relvance score calculation between
    /// two token sets.
    /// </summary>
    internal class RSVOkapiAlgorithm : IRSVAlgorithm
    {
        #region Constants

        internal const double alpha1 = 0.5d;
        internal const double alpha2 = 1.5d;

        #endregion

        #region Fields

        private double lc;
        private int nc;
        private Func<Token, int> nct;

        #endregion

        #region Constructors

        /// <summary>
        /// Creates instance of the Okapi formula implementation
        /// </summary>
        /// <param name="lc">Mean document transcription length</param>
        /// <param name="nc">total number of documents in the collection</param>
        /// <param name="nct">formula evaluating number of the documents containing t</param>
        internal RSVOkapiAlgorithm(double lc, int nc, Func<Token, int> nct)
        {
            this.lc = lc;
            this.nc = nc;
            this.nct = nct;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets / sets value of the Lc parameter of the Okapi formula.
        /// Lc is a mean document transcription length.
        /// </summary>
        internal double Lc
        {
            get
            {
                return this.lc;
            }
        }

        /// <summary>
        /// Gets / sets Nc parameter value. 
        /// Nc defines total number of documents in the collection.
        /// </summary>
        internal int Nc
        {
            get
            {
                return this.nc;
            }
        }

        /// <summary>
        /// Gets / sets delegate used for nc(t) formula evaluation
        /// </summary>
        internal Func<Token, int> Nct
        {
            get
            {
                return this.nct;
            }
        }

        #endregion

        #region IRSVAlgorithm Members

        /// <summary>
        /// Calculates a relevance score value (RSV) using Okapi algorithm
        /// </summary>
        /// <param name="Q">Query tokens set</param>
        /// <param name="D">Documents tokens set</param>
        /// <returns>Calculated RSV</returns>
        public double Calculate(Token[] Q, Token[] D)
        {
            double rsv = 0.0d;
            for (int i = 0; i < Q.Length; i++)
                rsv += (Ft(Q[i], Q) * Ft(Q[i], D) * Math.Log10(IDF(Q[i]))) / (alpha1 + alpha2 * Ld(D) / this.lc + Ft(Q[i], D));

            return rsv;
        }

        #endregion

        #region Support methods

        /// <summary>
        /// Calculates inverted density formula for the given token t
        /// </summary>
        /// <param name="t">Token from the query</param>
        /// <returns>IDF value</returns>
        private double IDF(Token t)
        {
            int nct = Nct(t);
            return ((double)(this.nc - nct) + 0.5d) / ((double)nct + 0.5d);
        }

        /// <summary>
        /// Calculates token frequency in the 
        /// </summary>
        /// <param name="t">Token t</param>
        /// <param name="T">Tokens set</param>
        /// <returns>Number of token occurances</returns>
        private double Ft(Token t, Token[] T)
        {
            double f = 0.0d;
            for (int i = 0; i < T.Length; i++)
                if (T[i].Value == t.Value)
                    f++;

            return f;
        }

        /// <summary>
        /// Calculates document transcription length
        /// </summary>
        /// <param name="D">Document tokens</param>
        /// <returns>Ld value</returns>
        private double Ld(Token[] D)
        {
            return (double)D.Length;
        }

        #endregion
    }
}
